
package cn.uncode.rpc.core.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.uncode.rpc.cluster.Cluster;
import cn.uncode.rpc.common.CommonConstant;
import cn.uncode.rpc.common.log.Logger;
import cn.uncode.rpc.common.log.LoggerFactory;
import cn.uncode.rpc.core.ApplicationInfo;
import cn.uncode.rpc.core.DefaultRequest;
import cn.uncode.rpc.core.Response;
import cn.uncode.rpc.core.URLParam;
import cn.uncode.rpc.exception.FrameworkException;
import cn.uncode.rpc.util.ExceptionUtil;
import cn.uncode.rpc.util.FrameworkUtil;
import cn.uncode.rpc.util.ReflectUtil;
import cn.uncode.rpc.util.RequestIdUtil;



/**
 * 
 * @author maijunsheng
 * 
 * @param <T>
 */
public class CallerInvocationHandler<T> implements InvocationHandler {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(CallerInvocationHandler.class);

    private List<Cluster<T>> clusters;
    private Class<T> clz;
//    private SwitcherService switcherService = null;

    public CallerInvocationHandler(Class<T> clz, Cluster<T> cluster) {
        this.clz = clz;
        this.clusters = new ArrayList<Cluster<T>>(1);
        this.clusters.add(cluster);

        init();
    }

    public CallerInvocationHandler(Class<T> clz, List<Cluster<T>> clusters) {
        this.clz = clz;
        this.clusters = clusters;

        init();
    }

    private void init() {
        // clusters 不应该为空
//        String switchName =
//                this.clusters.get(0).getUrl().getParameter(URLParamType.switcherService.getName(), URLParamType.switcherService.getValue());
//        switcherService = ExtensionLoader.getExtensionLoader(SwitcherService.class).getExtension(switchName);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        DefaultRequest request = new DefaultRequest();

        request.setRequestId(RequestIdUtil.getRequestId());
        request.setArguments(args);
        request.setMethodName(method.getName());
        request.setParamtersDesc(ReflectUtil.getMethodParamDesc(method));
        request.setInterfaceName(clz.getName());
        request.setAttachment(URLParam.REQUEST_ID_FROM_CLIENT.getName(), String.valueOf(RequestIdUtil.getRequestIdFromClient()));

        // 当 referer配置多个protocol的时候，比如A,B,C，
        // 那么正常情况下只会使用A，如果A被开关降级，那么就会使用B，B也被降级，那么会使用C
        for (Cluster<T> cluster : clusters) {
            String protocolSwitcher = CommonConstant.PROTOCOL_SWITCHER_PREFIX + cluster.getUrl().getProtocol();

//            Switcher switcher = switcherService.getSwitcher(protocolSwitcher);
//
//            if (switcher != null && !switcher.isOn()) {
//                continue;
//            }

            request.setAttachment(URLParam.VERSION.getName(), cluster.getUrl().getVersion());
            request.setAttachment(URLParam.CLIENT_GROUP.getName(), cluster.getUrl().getGroup());
            // 带上client的application和module
            request.setAttachment(URLParam.APPLICATION.getName(), ApplicationInfo.getApplication(cluster.getUrl()).getApplication());
            request.setAttachment(URLParam.MODULE.getName(), ApplicationInfo.getApplication(cluster.getUrl()).getModule());
            Response response = null;
            boolean throwException =
                    Boolean.parseBoolean(cluster.getUrl().getParameter(URLParam.THROW_EXCEPTION.getName(),
                            URLParam.THROW_EXCEPTION.getValue()));
            try {
                response = cluster.invoke(request);
                return response.getValue();
            } catch (RuntimeException e) {
                if (ExceptionUtil.isBizException(e)) {
                    Throwable t = e.getCause();
                    // 只抛出Exception，防止抛出远程的Error
                    if (t != null && t instanceof Exception) {
                        throw t;
                    } else {
                        String msg =
                                t == null ? "biz exception cause is null" : ("biz exception cause is throwable error:" + t.getClass()
                                        + ", errmsg:" + t.getMessage());
                        throw new FrameworkException(msg);
                    }
                } else if (!throwException) {
                	LOGGER.warn("RefererInvocationHandler invoke false, so return default value: uri=" + cluster.getUrl().getUri(), e);
                    return getDefaultReturnValue(method.getReturnType());
                } else {
                	LOGGER.error(
                            "RefererInvocationHandler invoke Error: uri=" + cluster.getUrl().getUri(), e);
                    throw e;
                }
            }
        }

        throw new FrameworkException("Referer call Error: cluster not exist, interface=" + clz.getName() + " "
                + FrameworkUtil.toString(request));

    }

    private Object getDefaultReturnValue(Class<?> returnType) {
        if (returnType != null && returnType.isPrimitive()) {
            return PrimitiveDefault.getDefaultReturnValue(returnType);
        }
        return null;
    }

    private static class PrimitiveDefault {
        private static boolean defaultBoolean;
        private static char defaultChar;
        private static byte defaultByte;
        private static short defaultShort;
        private static int defaultInt;
        private static long defaultLong;
        private static float defaultFloat;
        private static double defaultDouble;

        private static Map<Class<?>, Object> primitiveValues = new HashMap<Class<?>, Object>();

        static {
            primitiveValues.put(boolean.class, defaultBoolean);
            primitiveValues.put(char.class, defaultChar);
            primitiveValues.put(byte.class, defaultByte);
            primitiveValues.put(short.class, defaultShort);
            primitiveValues.put(int.class, defaultInt);
            primitiveValues.put(long.class, defaultLong);
            primitiveValues.put(float.class, defaultFloat);
            primitiveValues.put(double.class, defaultDouble);
        }

        public static Object getDefaultReturnValue(Class<?> returnType) {
            return primitiveValues.get(returnType);
        }

    }
}
